import { DropTarget } from 'react-dnd';
import { getElement } from '@design/core/utils/dom';

function calculateOffset(clientOffset, node) {
  const element = getElement(node.key);
  const offset = element.getBoundingClientRect();
  return {
    x: clientOffset.x - parseInt(offset.x, 10),
    y: clientOffset.y - parseInt(offset.y, 10),
  };
}

function convertOffset(offset, source, target) {
  const sourceRect = source.getBoundingClientRect();
  const targetRect = target.getBoundingClientRect();
  const delta = {
    x: targetRect.x - sourceRect.x,
    y: targetRect.y - sourceRect.y,
  };
  return {
    x: offset.x - delta.x,
    y: offset.y - delta.y,
  };
}

const dropSpec = {
  canDrop() {
    return true;
  },
  drop(props, monitor, component) {
    if (!monitor.isOver({ shallow: true })) {
      return;
    }
    const { isNew, node: source } = monitor.getItem();
    const { node, editor, parent } = props;
    let target = editor.isVoid(node) ? parent : node;
    if (source.key === target.key || source.contains(target.key)) {
      target = editor.value.document.getParent(source.key);
    }
    let offset = { x: 0, y: 0 };
    if (!isNew) {
      const difference = monitor.getDifferenceFromInitialOffset();
      const sourceParent = editor.value.document.getParent(source.key);
      offset = {
        x: source.position.x + difference.x,
        y: source.position.y + difference.y,
      };
      if (sourceParent !== target) {
        // 父节点变化时, 计算在新的坐标系下的x,y
        const sourceContainerEle = getElement(sourceParent.key);
        const targetEle = getElement(target.key);
        offset = convertOffset(offset, sourceContainerEle, targetEle);
      }
    } else {
      const clientOffset = monitor.getClientOffset();
      offset = calculateOffset(clientOffset, target);
    }
    return { target, editor, offset };
  },
  hover(props, monitor) {
    if (!monitor.isOver({ shallow: true })) {
      return;
    }
    const { element, isNew, node: source } = monitor.getItem();
    const { value } = props.editor;
    let offset = { x: 0, y: 0 };
    if (!isNew) {
      const difference = monitor.getDifferenceFromInitialOffset();
      offset = {
        x: source.position.x + difference.x,
        y: source.position.y + difference.y,
      };
    } else {
      const clientOffset = monitor.getClientOffset();
      offset = calculateOffset(clientOffset, value.document);
    }
    if (element) {
      element.style.top = `${offset.y}px`;
      element.style.left = `${offset.x}px`;
    }
  },
};

function dropCollect(connect, monitor) {
  const isOver = monitor.canDrop() && monitor.isOver({ shallow: true });
  return {
    connectDropTarget: connect.dropTarget(),
    isOver,
  };
}

function DropDecorator(options = {}) {
  const { type = 'block' } = options;
  return Comp => {
    return DropTarget(type, dropSpec, dropCollect)(Comp);
  };
}

export default DropDecorator;
